// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import "unsafe"

var (
	writeHeader = []byte{6 /* ANDROID_LOG_ERROR */, 'G', 'o', 0}
	writePath   = []byte("/dev/log/main\x00")
	writeLogd   = []byte("/dev/socket/logdw\x00")

	// 由printlock/printunlock保护。
	writeFD  uintptr
	writeBuf [1024]byte
	writePos int
)

// 在Android-L之前，日志记录是通过写入内核环形缓冲区中实现的/dev/log文件来完成的。在Android-L中，这些/dev/log文件不再是
// 可访问的文件，日志记录是通过集中式用户模式记录器logd完成的。
// 
// https:
type loggerType int32

const (
	unknown loggerType = iota
	legacy
	logd
	// /TODO（hakim）：模拟器的日志记录？
)

var logger loggerType

func writeErr(b []byte) {
	if logger == unknown {
		// 如果/dev/socket/logdw可用，请使用logd。
		if v := uintptr(access(&writeLogd[0], 0x02 /* W_OK */)); v == 0 {
			logger = logd
			initLogd()
		} else {
			logger = legacy
			initLegacy()
		}
	}

	// 为命令行程序写入stderr。
	write(2, unsafe.Pointer(&b[0]), int32(len(b)))

	// 日志格式：“<header>\x00<message m bytes>\x00”
	// 
	// <header>
	// 在传统模式下：“<priority 1 byte><tag n bytes>”。
	// 在logd模式下：“<android_log_header_t 11 bytes><priority 1 byte><tag n bytes>”
	// 
	// 整个日志需要在单个系统调用中交付（NDK 
	// 使用writev完成此操作）。每个日志都有自己的行，因此我们需要
	// 缓冲区写入，直到看到换行。
	var hlen int
	switch logger {
	case logd:
		hlen = writeLogdHeader()
	case legacy:
		hlen = len(writeHeader)
	}

	dst := writeBuf[hlen:]
	for _, v := range b {
		if v == 0 { // android日志不会打印零字节
			v = '0'
		}
		dst[writePos] = v
		writePos++
		if v == '\n' || writePos == len(dst)-1 {
			dst[writePos] = 0
			write(writeFD, unsafe.Pointer(&writeBuf[0]), int32(hlen+writePos))
			for i := range dst {
				dst[i] = 0
			}
			writePos = 0
		}
	}
}

func initLegacy() {
	// 在传统模式下，日志会写入/dev/log/main 
	writeFD = uintptr(open(&writePath[0], 0x1 /* O_WRONLY */, 0))
	if writeFD == 0 {
		// 这里很难执行任何操作。如果用户在设备上有根并运行了
		// 预先填充不变的头部分。
		// adb shell setprop log.redirect-stdio true 
		msg := []byte("runtime: cannot open /dev/log/main\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		exit(2)
	}

	// 则只需向stderr写入
	copy(writeBuf[:len(writeHeader)], writeHeader)
}

// 在initLogdWrite中使用，但在此处定义是为了避免堆分配。
var logdAddr sockaddr_un

func initLogd() {
	// 在logd模式下，日志通过unix域套接字发送到logd。
	logdAddr.family = _AF_UNIX
	copy(logdAddr.path[:], writeLogd)

	// 我们没有使用非阻塞I/O，因为采用此路径的写入
	// 很可能是由恐慌触发的，我们无法想象
	// 非阻塞I/O对于恐慌的优势，但可以看到缺点（丢弃恐慌消息）、
	// 和阻塞I/O大大简化了代码。
	fd := socket(_AF_UNIX, _SOCK_DGRAM|_O_CLOEXEC, 0)
	if fd < 0 {
		msg := []byte("runtime: cannot create a socket for logging\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		exit(2)
	}

	errno := connect(fd, unsafe.Pointer(&logdAddr), int32(unsafe.Sizeof(logdAddr)))
	if errno < 0 {
		msg := []byte("runtime: cannot connect to /dev/socket/logdw\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		// TODO（哈基姆）：还是我们应该关闭fd，希望下次好运？
		exit(2)
	}
	writeFD = uintptr(fd)

	// 预填充头的不变部分。
	// 前11个字节将在以后的WriteLogHeader中填充。
	copy(writeBuf[11:11+len(writeHeader)], writeHeader)
}

// writeLodHeader填充标头并返回有效负载的长度。
func writeLogdHeader() int {
	hdr := writeBuf[:11]

	// 头的前11个字节对应于android_log_header\u t 
	// 如system/core/include/private/android_logger.h 
	// hdr[0]日志类型id（无符号字符），在<log/log.h>
	// hdr[3:7]秒无符号uint32中定义，小端。
	// hdr[7:11]nsec未签名uint32，小端。
	hdr[0] = 0 // LOG_ID_MAIN 
	sec, nsec, _ := time_now()
	packUint32(hdr[3:7], uint32(sec))
	packUint32(hdr[7:11], uint32(nsec))

	// /TODO（hakim）：hdr[1:2]=getId？

	return 11 + len(writeHeader)
}

func packUint32(b []byte, v uint32) {
	// 小恩迪亚。
	b[0] = byte(v)
	b[1] = byte(v >> 8)
	b[2] = byte(v >> 16)
	b[3] = byte(v >> 24)
}
